/* Copyright  2009
   @Author
   Richard Changde Yin            e-mail yinchangde@hotmail.com

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
/*
 * @filename libclient.c
 */

#include <include/global.h>
#include "libclient_priv.h"

#include "libclient.h"
#include "clienterrmsg.h"
#include "pack.h"

#include <sys/stat.h>
#include <signal.h>
#include <time.h>
#ifdef   HAVE_PWD_H
#include <pwd.h>
#endif

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#ifdef HAVE_SELECT_H
#include <select.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#ifdef HAVE_POLL
#include <sys/poll.h>
#endif
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifndef INADDR_NONE
#define INADDR_NONE  -1
#endif

#include <stdlib.h>
#include <string.h>
#include <include/utf8.h>

uint32_t net_buffer_length= 8192;
uint32_t max_allowed_packet= 1024L*1024L*1024L;

unsigned int esql_port=0;

#include <errno.h>


static ESQL_PARAMETERS esql_internal_parameters=
{&max_allowed_packet, &net_buffer_length, 0};

const ESQL_PARAMETERS * client_get_parameters(void)
{
  return &esql_internal_parameters;
}

unsigned int client_get_default_port(void)
{
  return esql_port;
}

void client_set_default_port(unsigned int port)
{
  esql_port= port;
}

/*
  Expand wildcard to a sql string
*/

static void
append_wild(char *to, char *end, const char *wild)
{
  end-=5;          /* Some extra */
  if (wild && wild[0])
  {
    to= strcpy(to," like '");
    to+= 7; /* strlen(" like '"); */

    while (*wild && to < end)
    {
      if (*wild == '\\' || *wild == '\'')
  *to++='\\';
      *to++= *wild++;
    }
    if (*wild)          /* Too small buffer */
      *to++='%';        /* Nicer this way */
    to[0]='\'';
    to[1]=0;
  }
}

#if defined(HAVE_GETPWUID) && defined(NO_GETPWUID_DECL)
struct passwd *getpwuid(uid_t);
char* getlogin(void);
#endif

/**************************************************************************
  Do a query. If query returned rows, free old rows.
  Read data by client_store_result or by repeat call of client_fetch_row
**************************************************************************/

int client_query(ESQL *esql, const char *query)
{
  return client_real_query(esql,query, (uint32_t) strlen(query));
}


/**************************************************************************
  Return next field of the query results
**************************************************************************/

ESQL_FIELD *
client_fetch_field(ESQL_RES *result)
{
  if (result->current_field >= result->field_count)
    return(NULL);
  return &result->fields[result->current_field++];
}


/**************************************************************************
  Move to a specific row and column
**************************************************************************/

void
client_data_seek(ESQL_RES *result, uint64_t row)
{
  ESQL_ROWS  *tmp=0;
  if (result->data)
    for (tmp=result->data->data; row-- && tmp ; tmp = tmp->next) ;
  result->current_row=0;
  result->data_cursor = tmp;
}


/*************************************************************************
  put the row or field cursor one a position one got from ESQL_ROW_tell()
  This doesn't restore any data. The next client_fetch_row or
  client_fetch_field will return the next row or field after the last used
*************************************************************************/

ESQL_ROW_OFFSET
client_row_seek(ESQL_RES *result, ESQL_ROW_OFFSET row)
{
  ESQL_ROW_OFFSET return_value=result->data_cursor;
  result->current_row= 0;
  result->data_cursor= row;
  return return_value;
}


ESQL_FIELD_OFFSET
client_field_seek(ESQL_RES* result, ESQL_FIELD_OFFSET field_offset)
{
  ESQL_FIELD_OFFSET return_value=result->current_field;
  result->current_field=field_offset;
  return (ESQL_FIELD_OFFSET)return_value;
}


/*****************************************************************************
  List all tables in a database
  If wild is given then only the tables matching wild is returned
*****************************************************************************/

ESQL_RES *
client_list_tables(ESQL *esql, const char *wild)
{
  char buff[255];
  char *ptr= strcpy(buff, "show tables");
  ptr+= 11; /* strlen("show tables"); */

  append_wild(ptr,buff+sizeof(buff),wild);
  if (client_query(esql,buff))
    return(0);
  return (client_store_result(esql));
}


ESQL_FIELD *client_cli_list_fields(ESQL *esql)
{
  ESQL_DATA *query;
  if (!(query= client_cli_read_rows(esql,(ESQL_FIELD*) 0, 8)))
    return NULL;

  esql->field_count= (uint32_t) query->rows;
  return client_unpack_fields(query, esql->field_count, 1);
}

int
client_shutdown(ESQL *esql)
{
  return(simple_command(esql, COM_SHUTDOWN, 0, 0, 0));
}


const char *client_cli_read_statistics(ESQL *esql)
{
  esql->net.read_pos[esql->packet_length]=0;  /* End of stat string */
  if (!esql->net.read_pos[0])
  {
    client_set_error(esql, CR_WRONG_HOST_INFO, client_sqlstate_get_unknown());
    return esql->net.last_error;
  }
  return (char*) esql->net.read_pos;
}


int
client_ping(ESQL *esql)
{
  int res;
  res= simple_command(esql,COM_PING,0,0,0);
  if (res == CR_SERVER_LOST && esql->reconnect)
    res= simple_command(esql,COM_PING,0,0,0);
  return(res);
}


const char *
client_get_server_info(const ESQL *esql)
{
  return((char*) esql->server_version);
}


const char *
client_get_host_info(const ESQL *esql)
{
  return(esql->host_info);
}


uint32_t
client_get_proto_info(const ESQL *esql)
{
  return (esql->protocol_version);
}

const char *
client_get_client_info(void)
{
  return (char*) VERSION;
}

uint32_t client_get_client_version(void)
{
  return COLLAPSAR_VERSION_ID;
}

bool client_eof(const ESQL_RES *res)
{
  return res->eof;
}

const ESQL_FIELD * client_fetch_field_direct(const ESQL_RES *res, unsigned int fieldnr)
{
  return &(res)->fields[fieldnr];
}

const ESQL_FIELD * client_fetch_fields(const ESQL_RES *res)
{
  return res->fields;
}

ESQL_ROW_OFFSET client_row_tell(const ESQL_RES *res)
{
  return res->data_cursor;
}

ESQL_FIELD_OFFSET client_field_tell(const ESQL_RES *res)
{
  return res->current_field;
}



unsigned int  client_field_count(const ESQL *res)
{
  return res->field_count;
}

uint64_t client_affected_rows(const ESQL *esql)
{
  return esql->affected_rows;
}

uint64_t client_insert_id(const ESQL *esql)
{
  return esql->insert_id;
}

const char *  client_sqlstate(const ESQL *esql)
{
  return esql ? esql->net.sqlstate :  client_sqlstate_get_cant_connect();
}

uint32_t client_warning_count(const ESQL *esql)
{
  return esql->warning_count;
}

const char * client_info(const ESQL *esql)
{
  return esql->info;
}

uint32_t client_thread_id(const ESQL *esql)
{
  return esql->thread_id;
}

/****************************************************************************
  Some support functions
****************************************************************************/

/*
  Functions called my client_net_init() to set some application specific variables
*/

void  client_net_local_init(NET *net)
{
  net->max_packet=   (uint32_t) net_buffer_length;
  client_net_set_read_timeout(net, CLIENT_NET_READ_TIMEOUT);
  client_net_set_write_timeout(net, CLIENT_NET_WRITE_TIMEOUT);
  net->retry_count=  1;
  net->max_packet_size= (net_buffer_length > max_allowed_packet) ?
    net_buffer_length : max_allowed_packet;
}

/*
  Add escape characters to a string (blob?) to make it suitable for a insert
  to should at least have place for length*2+1 chars
  Returns the length of the to string
*/

uint32_t
client_escape_string(char *to,const char *from, uint32_t length)
{
  const char *to_start= to;
  const char *end, *to_end=to_start + 2*length;
  bool overflow= false;
  for (end= from + length; from < end; from++)
  {
    uint32_t tmp_length;
    char escape= 0;
    if (!U8_IS_SINGLE(*from))
    {
      tmp_length= U8_LENGTH(*(uint32_t*)from);
      if (to + tmp_length > to_end)
      {
        overflow= true;
        break;
      }
      while (tmp_length--)
        *to++= *from++;
      from--;
      continue;
    }
    switch (*from) {
    case 0:                             /* Must be escaped for 'mysql' */
      escape= '0';
      break;
    case '\n':                          /* Must be escaped for logs */
      escape= 'n';
      break;
    case '\r':
      escape= 'r';
      break;
    case '\\':
      escape= '\\';
      break;
    case '\'':
      escape= '\'';
      break;
    case '"':                           /* Better safe than sorry */
      escape= '"';
      break;
    case '\032':                        /* This gives problems on Win32 */
      escape= 'Z';
      break;
    }
    if (escape)
    {
      if (to + 2 > to_end)
      {
        overflow= true;
        break;
      }
      *to++= '\\';
      *to++= escape;
    }
    else
    {
      if (to + 1 > to_end)
      {
        overflow= true;
        break;
      }
      *to++= *from;
    }
  }
  *to= 0;
  return overflow ? (size_t) -1 : (size_t) (to - to_start);
}

int  client_cli_unbuffered_fetch(ESQL *esql, char **row)
{
  if (packet_error ==  client_cli_safe_read(esql))
    return 1;

  *row= ((esql->net.read_pos[0] == COLLAPSAR_PROTOCOL_NO_MORE_DATA) ? NULL :
   (char*) (esql->net.read_pos+1));
  return 0;
}

/********************************************************************
 Transactional APIs
*********************************************************************/

/*
  Commit the current transaction
*/

bool client_commit(ESQL *esql)
{
  return((bool)  client_real_query(esql, "commit", 6));
}

/*
  Rollback the current transaction
*/

bool  client_rollback(ESQL *esql)
{
  return((bool) client_real_query(esql, "rollback", 8));
}


/*
  Set autocommit to either true or false
*/

bool client_autocommit(ESQL *esql, bool auto_mode)
{
  return((bool)  client_real_query(esql, auto_mode ?
                                         "set autocommit=1":"set autocommit=0",
                                         16));
}


/********************************************************************
 Multi query execution + SPs APIs
*********************************************************************/

/*
  Returns true/false to indicate whether any more query results exist
  to be read using client_next_result()
*/

bool client_more_results(const ESQL *esql)
{
  return (esql->server_status & SERVER_MORE_RESULTS_EXISTS) ? true:false;
}


/*
  Reads and returns the next query results
*/
int client_next_result(ESQL *esql)
{
  if (esql->status != ESQL_STATUS_READY)
  {
    client_set_error(esql, CR_COMMANDS_OUT_OF_SYNC,  client_sqlstate_get_unknown());
    return(1);
  }

  client_net_clear_error(&esql->net);
  esql->affected_rows= ~(uint64_t) 0;

  if (esql->server_status & SERVER_MORE_RESULTS_EXISTS)
    return((*esql->methods->next_result)(esql));

  return(-1);        /* No more results */
}


ESQL_RES * client_use_result(ESQL *esql)
{
  return (*esql->methods->use_result)(esql);
}

bool client_read_query_result(ESQL *esql)
{
  return (*esql->methods->read_query_result)(esql);
}

